﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Collections.Specialized;
using System.Threading;
using Bencode;
using JunctionPoints;

namespace uTorrentJunctions
{
    public partial class MainForm : Form
    {
        uTorrentJunctions ut; // Main data class
        private bool loadingLabels = false; // Flag to maintain user selection during updates
        private bool loadingTrackers = false; // Flag to maintain user selection during updates
        private FileSystemWatcher fsw = null; // Monitors changes to resume.dat/dir
        private delegate void fswEventMethod();
        private fswEventMethod fswDelegate;
        private FormWindowState lastWindowState; // When restoring from tray
        private bool isMinimized;
        private bool minimizeToTray;
        private int listColumnOffset = 4;
        
        public MainForm()
        {
            InitializeComponent();
            listLabels.Columns[0].Width = listLabels.Width - listColumnOffset;
            listTrackers.Columns[0].Width = listTrackers.Width - listColumnOffset;
            fswDelegate = new fswEventMethod(FileMonitoringDelegate);
        }

        private void MainForm_Load(object sender, EventArgs e)
        {
            // Load saved settings and attempts loading resume.dat
            ut = new uTorrentJunctions();
            LoadSettings();
            TrayIconSetup();
            
            // Auto-load
            if (ut.IsReady())
            {
                ut.DecodeResumeDat();
                this.UpdateLabels();
                this.UpdateTrackers();
                this.UpdateLog();
            }
        }

        private void menuExit_Click(object sender, EventArgs e)
        {
            this.Close();
        }
        private void MainForm_Resize(object sender, EventArgs e)
        {
            if (FormWindowState.Minimized == this.WindowState)
            {
                //this.notifyIcon.ShowBalloonTip(500);
                isMinimized = true;
                if (minimizeToTray)
                {
                    this.Hide();
                }
            }
            else if (FormWindowState.Normal == this.WindowState)
            {
                lastWindowState = this.WindowState;
                isMinimized = false;
            }
            else if (FormWindowState.Maximized == this.WindowState)
            {
                lastWindowState = this.WindowState;
                isMinimized = false;
                //this.notifyIcon.Visible = false;
            }
        }
        private void TrayIconSetup()
        {
            //notifyIcon = new System.Windows.Forms.NotifyIcon();
            //notifyIcon.BalloonTipText = "Ballon minimize text";
            //notifyIcon.BalloonTipTitle = "Ballon minimize title";
            //notifyIcon.Text = "Junctions for uTorrent Labels";
            /*notifyIcon.Icon = new System.Drawing.Icon(
                       System.Reflection.Assembly.GetExecutingAssembly()
                           .GetManifestResourceStream("uJunctions.ico"));*/
            lastWindowState = this.WindowState;
        }
        private void TrayIconClick(Object sender, EventArgs e)
        {
            if (isMinimized)
            {
                this.Show();
                this.WindowState = lastWindowState;
                this.Activate();
            }
            else
            {
                this.WindowState = FormWindowState.Minimized;
                this.Hide();
            }
        }
        private void minimizeToTrayToolStripMenuItem_Click(object sender, EventArgs e)
        {
            minimizeToTray = minimizeToTrayToolStripMenuItem.Checked;
            this.notifyIcon.Visible = minimizeToTrayToolStripMenuItem.Checked;
        }
        private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Show about box
            using (AboutBox test = new AboutBox())
            {
                test.ShowDialog();
            }
        }
        private void CleanSettings()
        {
            // Remove any non-existent labels and trackers from user configuration?
        }

        private void cmdJunctionPath_Click(object sender, EventArgs e)
        {
            // Show open folder dialog
            if (openJunctionPath.ShowDialog() == DialogResult.OK)
            {
                statusLabel.Text = "";
                txtJunctionPath.Text = openJunctionPath.SelectedPath;
                RemoveJunctionsToolStripMenuItem.Enabled = true;
            }
        }
       
        private void cmdUpdateJunctions_Click(object sender, EventArgs e)
        {
            if (!ut.IsReady())
            {
                MessageBox.Show("In order to proceed, the following elements must be configured: \n\n - Resume.dat location\n - Junctions folder\n - At least one selected label", "Configuration incomplete", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                return;
            }

            // Set up FileSystemWatcher
            // uTorrent 3.x creates a temp file, renames resume.dat into resume.old then the temp file into resume.dat
            fsw = new FileSystemWatcher(Path.GetDirectoryName(ut.resumeLocation), Path.GetFileName(ut.resumeLocation));
            fsw.NotifyFilter = NotifyFilters.FileName;
            fsw.Renamed += new RenamedEventHandler(FileMonitoringEvent);
            fsw.EnableRaisingEvents = true;
            this.LogAdd("Monitoring started.");

            // Perform an UpdateJunction
            ut.UpdateJunctions();
            this.UpdateLabels();
            this.UpdateTrackers();
            this.UpdateLog();

            // Disable some controls
            cmdUpdateJunctions.Enabled = false;
            cmdUpdateJunctionsStop.Enabled = true;
            txtJunctionPath.Enabled = false;
            cmdJunctionPath.Enabled = false;
            cmdResumeDatLocation.Enabled = false;
            openToolStripMenuItem.Enabled = false;
        }
        private void FileMonitoringEvent(object source, FileSystemEventArgs e)
        {
            // Called from FileSystemWatcher on resume.dat changes
            // Runs in separate thread
            Thread.Sleep(1000); // Allow file to be available
            this.Invoke(this.fswDelegate);
        }
        private void FileMonitoringDelegate()
        {
            // Invoked delegate, runs in GUI thread
            ut.DecodeResumeDat();
            ut.UpdateJunctions();
            this.UpdateLabels();
            this.UpdateTrackers();
            this.UpdateLog();
        }

        private void cmdUpdateJunctionsStop_Click(object sender, EventArgs e)
        {
            // Stop monitoring
            fsw.EnableRaisingEvents = false;
            LogAdd("Monitoring stopped.");
            
            // Enable some controls
            cmdUpdateJunctions.Enabled = true;
            cmdUpdateJunctionsStop.Enabled = false;
            txtJunctionPath.Enabled = true;
            cmdJunctionPath.Enabled = true;
            cmdResumeDatLocation.Enabled = true;
            openToolStripMenuItem.Enabled = true;
        }
        
        private void LogAdd(String insert)
        {
            // Adds item to logger with timestamp
            listLog.Items.Add("[" + DateTime.Now.ToString("hh:mm") + "] " + insert);
            listLog.Items[listLog.Items.Count - 1].EnsureVisible();
            listLog.Refresh();
        }
        
        private void listLabels_ColumnClick(object sender, ColumnClickEventArgs e)
        {
            // Sort Labels.
            loadingLabels = true;
            switch (listLabels.Sorting)
            {
                case SortOrder.None:
                    listLabels.Sorting = SortOrder.Ascending;
                    break;
                case SortOrder.Ascending:
                    listLabels.Sorting = SortOrder.Descending;
                    break;
                case SortOrder.Descending:
                    listLabels.Sorting = SortOrder.Ascending;
                    break;
            }
            loadingLabels = false;
        }
        
        private void listLabels_ColumnWidthChanged(object sender, ColumnWidthChangedEventArgs e)
        {
            // Maintains column width
            if ((listLabels.Columns[0].Width != listLabels.Width - listColumnOffset) && (listLabels.Width > listColumnOffset))
            {
                listLabels.Columns[0].Width = listLabels.Width - listColumnOffset;
            }
        }
        
        private void listLabels_ColumnWidthChanging(object sender, ColumnWidthChangingEventArgs e)
        {
            // Prevent manual column width change
            e.Cancel = true;
            listLabels.Refresh();
        }
        
        private void listLabels_ItemChecked(object sender, ItemCheckedEventArgs e)
        {
            // Maintains internal list of selected labels
            if (!loadingLabels)
            {
                // Replace selectedLabels with checked items
                ut.selectedLabels.Clear();
                foreach (ListViewItem i in listLabels.Items)
                {
                    
                    Properties.Settings.Default.SelectedLabels = new System.Collections.Specialized.StringCollection();
                    if (i.Checked)
                    {
                        ut.selectedLabels.Add(i.Name);
                    }
                }
                // Update Trackers
                UpdateTrackers();
            }
        }
        
        private void listLabels_Resize(object sender, EventArgs e)
        {
            // Maintains column width on window resize
            listLabels_ColumnWidthChanged(null, null);
        }
        
        private void listLog_Resize(object sender, EventArgs e)
        {
            // Maintains column width
            if ((listLog.Columns[0].Width != listLog.Width - listColumnOffset) && (listLog.Width > listColumnOffset))
            {
                listLog.Columns[0].Width = listLog.Width - listColumnOffset;
            }
        }
        
        private void listTrackers_ColumnClick(object sender, ColumnClickEventArgs e)
        {
            // Change trackers sort order
            loadingTrackers = true;
            switch (listTrackers.Sorting)
            {
                case SortOrder.None:
                    listTrackers.Sorting = SortOrder.Ascending;
                    break;
                case SortOrder.Ascending:
                    listTrackers.Sorting = SortOrder.Descending;
                    break;
                case SortOrder.Descending:
                    listTrackers.Sorting = SortOrder.Ascending;
                    break;
            }
            loadingTrackers = false;
        }
        
        private void listTrackers_ColumnWidthChanged(object sender, ColumnWidthChangedEventArgs e)
        {
            // Maintain column width
            if ((listTrackers.Columns[0].Width != listTrackers.Width - listColumnOffset) && (listTrackers.Width > listColumnOffset))
            {
                listTrackers.Columns[0].Width = listTrackers.Width - listColumnOffset;
            }
        }
        
        private void listTrackers_ColumnWidthChanging(object sender, ColumnWidthChangingEventArgs e)
        {
            // Prevent manual column width change
            e.Cancel = true;
            listTrackers.Refresh();
        }
        
        private void listTrackers_ItemChecked(object sender, ItemCheckedEventArgs e)
        {
            // Maintains internal list of skipped trackers
            
            // Update skippedTrackers (always skip if called from code)
            if (!loadingTrackers && e != null)
            {
                if (e.Item.Checked == true)
                {
                    if (!ut.skippedTrackers.Contains(e.Item.Name))
                    {
                        ut.skippedTrackers.Add(e.Item.Name);
                    }
                }
                else
                {
                    ut.skippedTrackers.Remove(e.Item.Name);
                }

            }

            // User action during monitoring causes UpdateJunction
            if (!loadingTrackers)
            {
                //if (ut.monitoring)
                //{
                    //UpdateJunctions();
                //}
            }
        }

        private void listTrackers_Resize(object sender, EventArgs e)
        {
            // Maintains column width on window resize
            listTrackers_ColumnWidthChanged(null, null);
        }

        private void cmdResumeDatLocation_Click(object sender, EventArgs e)
        {
            openToolStripMenuItem_Click(null, null);
        }


        private void cmdManualUpdate_Click(object sender, EventArgs e)
        {
            if (!ut.IsReady())
            {
                MessageBox.Show("In order to proceed, the following elements must be configured: \n\n - Resume.dat location\n - Junctions folder\n - At least one selected label", "Configuration incomplete", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                return;
            }

            ut.UpdateJunctions();
            this.UpdateLog();
        }

        private void LoadSettings()
        {
            // Load settings from file

            // Junctions Path
            if (Directory.Exists(Properties.Settings.Default.JunctionsPath))
            {
                txtJunctionPath.Text = Properties.Settings.Default.JunctionsPath;
                ut.junctionsPath = txtJunctionPath.Text;
                RemoveJunctionsToolStripMenuItem.Enabled = true;
            }
            // ResumeDatLocation
            if (String.IsNullOrEmpty(Properties.Settings.Default.ResumeDatLocation))
            {
                String testPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\uTorrent\\resume.dat";
                if (File.Exists(testPath))
                {
                    ut.resumeLocation = testPath;
                    txtResumeDatLocation.Text = testPath;
                    menuRefresh.Enabled = true;
                    // Load
                    ut.DecodeResumeDat();
                    UpdateLabels();
                    UpdateTrackers();
                    UpdateLog();
                }
            }
            else
            {
                ut.resumeLocation = Properties.Settings.Default.ResumeDatLocation;
                txtResumeDatLocation.Text = ut.resumeLocation;
            }

            // selectedLabels
            if (Properties.Settings.Default.SelectedLabels != null)
            {
                foreach (String s in Properties.Settings.Default.SelectedLabels)
                {
                    ut.selectedLabels.Add(s);
                }
            }

            // skippedTrackers
            if (Properties.Settings.Default.SkippedTrackers != null)
            {
                foreach (String s in Properties.Settings.Default.SkippedTrackers)
                {
                    ut.skippedTrackers.Add(s);
                }
            }

            // IgnoreIncomplete
            ut.ignoreIncomplete = Properties.Settings.Default.IgnoreIncomplete;
            ignoreIncompleteTorrentsToolStripMenuItem.Checked = ut.ignoreIncomplete;

            // MinimizeToTray
            minimizeToTray = Properties.Settings.Default.MinimizeToTray;
            minimizeToTrayToolStripMenuItem.Checked = minimizeToTray;
            this.notifyIcon.Visible = minimizeToTray;
        }
        private void saveSettingsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Save settings to file

            // JunctionsPath
            Properties.Settings.Default.JunctionsPath = txtJunctionPath.Text;

            // ResumeDatLocation
            Properties.Settings.Default.ResumeDatLocation = ut.resumeLocation;

            // SelectedLabels
            StringCollection sc = new StringCollection();
            foreach (String s in ut.selectedLabels)
            {
                sc.Add(s);
            }
            Properties.Settings.Default.SelectedLabels = sc;

            // SkippedTrackers
            sc = new StringCollection();
            foreach (String s in ut.skippedTrackers)
            {
                sc.Add(s);
            }
            Properties.Settings.Default.SkippedTrackers = sc;

            // IgnoreIncomplete
            Properties.Settings.Default.IgnoreIncomplete = ut.ignoreIncomplete;

            // Minimize to Tray
            Properties.Settings.Default.MinimizeToTray = minimizeToTray;

            // Save
            Properties.Settings.Default.Save();
        }
        private void menuAbout_Click(object sender, EventArgs e)
        {
            using (AboutBox test = new AboutBox())
                {
                    test.ShowDialog();
                }
        }
        private void menuIgnoreIncomplete_Click(object sender, EventArgs e)
        {
            if (menuIgnoreIncomplete.Checked)
            {
                ut.ignoreIncomplete = true;
            }
            else
            {
                ut.ignoreIncomplete = false;
            }
            if (ut.IsReady())
            {
                this.UpdateLabels();
                this.UpdateTrackers();
            }
        }
        private void menuRefresh_Click(object sender, EventArgs e)
        {
            if (File.Exists(ut.resumeLocation))
            {
                ut.DecodeResumeDat();
                UpdateLabels();
                UpdateTrackers();
                UpdateLog();

                if (fsw != null && fsw.EnableRaisingEvents == true)
                {
                    // If monitoring, UpdateJunctions after reload
                    ut.UpdateJunctions();
                    this.UpdateLabels();
                    this.UpdateTrackers();
                    this.UpdateLog();
                }
            }
        }

        private void openToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Show open file dialog and attempts loading resume.dat
            if (openDat.ShowDialog() == DialogResult.OK)
            {
                ut.resumeLocation = openDat.FileName;
                txtResumeDatLocation.Text = ut.resumeLocation;
                menuRefresh.Enabled = true;
                ut.DecodeResumeDat();
                UpdateLabels();
                UpdateTrackers();
                UpdateLog();
            }
        }
        private void RemoveJunctionsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Delete all junctions in target folder
            String[] folders = Directory.GetDirectories(txtJunctionPath.Text);
            int deleted = 0;
            foreach (String f in folders)
            {
                if (JunctionPoint.Exists(f))
                {
                    JunctionPoint.Delete(f);
                    deleted++;
                }
            }
            if (deleted == 0)
            {
                LogAdd("No junctions found in target folder.");
            }
            else
            {
                LogAdd(deleted.ToString() + " junctions removed.");
            }
        }
        private void txtJunctionPath_TextChanged(object sender, EventArgs e)
        {
            ut.junctionsPath = txtJunctionPath.Text;
        }
        private void showHideToolStripMenuItem_Click(object sender, EventArgs e)
        {
            TrayIconClick(null, null);
        }
        private void exitToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            menuExit_Click(null, null);
        }

        #region Data
        private void UpdateLabels()
        {
            // Refresh labels from uJunctions
            loadingLabels = true;
            listLabels.BeginUpdate();
            listLabels.Items.Clear();

            Dictionary<String, int> filteredLabels = ut.GetLabels();
            foreach (KeyValuePair<String, int> l in filteredLabels)
            {
                ListViewItem newItem = listLabels.Items.Add(l.Key + " (" + l.Value.ToString() + ")");
                newItem.Name = l.Key;
                if (ut.selectedLabels.Contains(newItem.Name))
                {
                    newItem.Checked = true;
                }
            }
            listLabels.EndUpdate();

            // Update tristate checkbox (will also update trackers)
            loadingLabels = false;
            listLabels_ItemChecked(null, null);
        }
        private void UpdateTrackers()
        {
            // Update list of trackers matching label filter and apply skipped selection
            loadingTrackers = true;
            listTrackers.BeginUpdate();
            listTrackers.Items.Clear();

            // Get trackers (key) and item count (value)
            Dictionary<String, int> filteredTrackers = ut.GetTrackers();

            // Add to list
            foreach (KeyValuePair<String, int> l in filteredTrackers)
            {
                ListViewItem newItem = listTrackers.Items.Add(l.Key + " (" + l.Value.ToString() + ")");
                newItem.Name = l.Key;

                if ((ut.skippedTrackers.Contains(newItem.Name)))
                {
                    newItem.Checked = true;
                }
                else
                {
                    newItem.Checked = false;
                }
            }
            listTrackers.EndUpdate();

            // Update tristate checkbox (will also update Torrents)
            loadingTrackers = false;
            listTrackers_ItemChecked(null, null);
        }
        private void UpdateLog()
        {
            if (ut.log.Count > 0)
            {
                listLog.BeginUpdate();
                if (listLog.Items.Count > 1000)
                {
                    listLog.Items.RemoveAt(0);
                }
                while (ut.log.Count > 0)
                {
                    listLog.Items.Add(ut.log.Dequeue());
                }
                listLog.Items[listLog.Items.Count - 1].EnsureVisible(); // Scroll down
                listLog.Refresh();
                listLog.EndUpdate();
            }
        }
        #endregion

        public struct MyStruct
        {
            public string name;
            public int size;
            public List<string> elements;
        }

        IEnumerable<NameElement> GetResults(Dictionary<string, MyStruct> dict)
        {
            foreach (KeyValuePair<string, MyStruct> t in dict)
                foreach (string v in t.Value.elements)
                    yield return new NameElement { name = t.Key, element = v };
        }

        private void button1_Click(object sender, EventArgs e)
        {
            // TESTING
            string x;
            MyStruct myStruct = new MyStruct();
            Dictionary<String, MyStruct> dict = new Dictionary<string, MyStruct>();

            // Populate 2 items
            myStruct.name = "item1";
            myStruct.elements = new List<string>();
            myStruct.elements.Add("elementA");
            dict.Add(myStruct.name, myStruct);

            myStruct.name = "item2";
            myStruct.elements = new List<string>();
            myStruct.elements.Add("elementA");
            myStruct.elements.Add("elementB");
            dict.Add(myStruct.name, myStruct);

            var q = GetResults(dict);

            MessageBox.Show(q.Count().ToString());

            //foreach (var g in groups)
                //Console.WriteLine(g.Key + ": " + g.Count());
            
            /*var newDictionary =
                from pair in dict
                from element in pair.Value
                select new { key = pair.key, value = element };*/
            
            /*var q = dict.SelectMany(t => t.Value.Select(v => new { name = t.Key, element = v }));*/
            //MessageBox.Show(q.Count().ToString()); // Returns 2
            //MessageBox.Show(q.Count().ToString()); // Returns 2
        }

        
    }
}

        class NameElement
{
    public string name { get; set; }
    public string element { get; set; }
}